#include "error.h"
#include "xml.h"
#include "io.h"
#include "simple.h"
#include "crypt.h"
#include "list.h"
#include "dict.h"
#include "xmldict/xmldict.h"
#include "defines.h"

struct elog_xml_doc *_elog_xml_meta_data;



char *elog_xml_endTag(const char *tag);
// Returns the end tag version of the opening tag


char *dat__cat(char *dest, const char *x)
{
	while (*dest)
		dest++;
	int c=0;
	int off = 0;
	while (x[c]) {
	  if (x[c] == '>' || x[c] == '<') {
		if (c) {
		  if (x[c-1] != '\\') {
			dest[c+off] = '\\';
			++off;
		  }
		} else {
		  dest[c+off] = '\\';
		  ++off;
		}
	  }
	  dest[c+off] = x[c];
	  ++c;
	}
	dest[c+off] = '\0';
	return &(dest[c]);
}

//this function is intelligent about escapes, it adds them!
char *xml__cat(char *dest, const char *x)
{
  	while (*dest)
		dest++;
	while ((*dest++ = *x++));	//apparently this works?
	return --dest;
}

char *elog_xml_getTag(char *doc, char *tagName[])
{
	char *d_s = doc;
	int c = 0;
	while (d_s != NULL && tagName[c] != NULL) {
		char *g = d_s;
		d_s = elog_xml_tagCapture(d_s, tagName[c]);
		free(g);
		++c;
	}
	if (d_s == NULL)
		return NULL;
	else if (tagName[c] == NULL)
		return d_s;

	free(d_s);
	return NULL;
}

char *elog_xml_tagCapture(const char *doc, char *tag)
{
	char *begin = strstr(doc, tag);
	if (begin != NULL) {
		char *tag_end = elog_xml_endTag(tag);

		if (tag_end == NULL)
			return NULL;
		char *end = strstr(begin, tag_end);

		if (end != NULL) {
			begin = &begin[strlen(tag)];	// removing opening tag
			int diff = end - begin;
			char *x;
			x = malloc((sizeof *x) * (diff + 1));
			memcpy(x, begin, diff);

			x[diff] = '\0';
			free(tag_end);
			return x;	// we got it!
		} else {
			free(tag_end);
			return NULL;
		}
		begin = strstr(doc, tag);
	}
	// it wasn't there, return null
	return NULL;
}

char *elog_xml_endTag(const char *tag)
{
	unsigned int len = strlen(tag);
	char *tag_end;
	tag_end = malloc((sizeof *tag_end) * (len + 2));	// remember, we
	// gotta include
	// the \0
	tag_end[len + 1] = '\0';
	tag_end[0] = '<';
	tag_end[1] = '/';
	strcpy(&(tag_end[2]), &(tag[1]));

	return tag_end;
}

int elog_xml_unreadify(char *xml)
{
  /*
	Modes:
	0  -- starting.
	1  -- Inside an open tag
	2  -- After an opening tag
	3  -- After a closing tag
	4  -- Inside a close tag
	5  -- Data
  */
  int mode = 0;
  int parens = 0;
  int c=0;
  int offset = 0;
  int data = 0;
  for (c=0; xml[c]; ++c) {
	xml[c - offset] = xml[c];
	switch (xml[c]) {
	case '>': {
	  if (mode == 1) mode = 2;
	  if (mode == 4) mode = 3;
	  data = 0;
	  continue;
	}
	case '<': {
	  if ((data && xml[c-1] != '\\' && (!parens)) ||
		  !data) {
		if (xml[c+1] == '/') 
		  mode = 4;
		else
		  mode = 1;
		data = 0;
	  }
	  continue;
	}
	case '"': {
	  //parens = 1 - parens;
	  continue;
	}
	}
	if (xml[c] == '\t' || xml[c] == '\r' || 
		xml[c] == '\n' || xml[c] == ' ') {
	  if ((mode == 2 || mode == 3) && !(data))
		++offset;
	}
	else if (mode == 2 || mode == 3) {
	  data = 1;
	}
  }
  xml[c-offset] = '\0';
  return c;
}

char *elog_xml_readify(const char *xml, int length)
{
  /*
	Modes:
	0  -- starting.
	1  -- Inside an open tag
	2  -- After an opening tag
	3  -- After a closing tag
	4  -- Inside a close tag
  */
  /*
	already:
	0  -- nothing
	1  -- a return
	2  -- a return and tab
	3  -- a return and 2 tabs
  */
  int size = length;
  if (size < 0)
	size = strlen(xml);
  int extra = 0;
  int escape = 0;
  int indent = 0;
  int mode = 0;
  int c=0;
  for (c=0; c < size; ++c) {
	if (escape) {
	  if (xml[c] == '\"')
		escape = 0;
	  continue;
	} else if (xml[c] == '\"') {
	  escape = 1;
	  continue;
	} else if (xml[c] == '\\') {
	  ++c;
	  continue;
	}  //end escape code


	switch (xml[c]) {
	case '<': {
	  if (mode == 1 || mode == 4) {
		elog_err_print("Syntax error in document\n");
		return NULL;
	  } else if (mode == 2) {
		if (xml[c+1] != '/') { 
		  ++indent;
		  extra += indent + 1; //for the \n
		  mode = 1;
		}
		else {
		  mode = 4;
		  //don't do anything.
		}
	  } else if (mode == 3) {
		if (xml[c+1] != '/') {
		  extra += indent;
		  mode = 1;
		}
		else {
		  extra += (indent-1) ; //for the \n and indent
		  indent -= 1;
		  mode = 4;
		}
	  } else if (mode == 0) {
		mode = 1;
		indent = 1;
	  } 
	  break;
	}
	case '>': {
	  if (mode == 1)
		mode = 2;
	  if (mode == 4)
		mode = 3;
	  break;
	}
	}
  }

  char *ret = malloc(size + extra + 2);

  //this second loop is largely the same as the first loop!
  extra = 0;
  escape = 0;
  indent = 0;
  mode = 0;
  c=0;
  int j=0;
  for (c=0; c < size; ++c) {
	if (escape) {
	  if (xml[c] == '\"')
		escape = 0;
	  ret[j] = xml[c];
	  ++j;
	  continue;
	} else if (xml[c] == '\"') {
	  ret[j] = xml[c];
	  ++j;
	  escape = 1;
	  continue;
	} else if (xml[c] == '\\') {
	  ret[j] = xml[c];
	  ret[j+1] = xml[c+1];
	  j+=2;
	  ++c;
	  continue;
	} //end escape code


	switch (xml[c]) {
	case '<': {
	  if (mode == 2) {
		if (xml[c+1] != '/') {
		  ret[j] = '\n';
		  int i=1;
		  for (i=1; i <= indent; ++i) 
			ret[j+i] = '\t';
		  j += (indent + 1);

		  ++indent;
		  mode = 1;
		}
		else {
		  mode = 4;
		  //don't do anything.
		}
	  } else if (mode == 3) {
		if (xml[c+1] != '/') {
			ret[j] = '\n';
		  int i=1;
		  for (i=1; i <= indent-1; ++i)
			ret[j+i] = '\t';
		  j += indent ;

		  mode = 1;
		}
		else {
		  ret[j] = '\n';
		  int i=1;
		  for (i=1; i < (indent -1); ++i)
			ret[j+i] = '\t';
		  j += (indent -1) ;

		  indent -= 1;
		  mode = 4;
		}
	  } else if (mode == 0) {
		mode = 1;
		indent = 1;
	  }
	  break;
	}
	case '>': {
	  if (mode == 1)
		mode = 2;
	  if (mode == 4)
		mode = 3;
	  break;
	}
	}
	ret[j] = xml[c];
	++j;
  }
  ret[j] = '\0';
  return ret;
}

char *elog_xml_start(const char *tag, unsigned int size)
{
	char *endTag = elog_xml_endTag(tag);
	char *ret = malloc((sizeof *ret) * (size + 1));
	/* Not absulutely necessary: */
	memset(ret, '\0', size);
	/* end unecessary */
	
	ret[0] = '\0';
	strcat(ret, tag);
	//strcat(ret, "\n");
	strcat(ret, endTag);
	//strcat(ret, "\n");
	free(endTag);	
	return ret;
}

int elog_xml_printf(char *xml, char *template, const char *tag, 
				 const char *data) 
{
	if (data == NULL)
		data = "";
	int len_ins = strlen(tag) * 2;

	char **operations = elog_sp_breakToArr(template, '.');

	char *spot = NULL;
	int c=0;
	int len_xml = strlen(xml);
	int tag_index[2] = {0, 0};
	int op = 0;
	while (c < len_xml) {
	  if (xml[c] == '\\') {
		c+=2;
		continue;
	  }
	  if (xml[c] == '<') {
		if (xml[c+1] == '/') {
		  char *loc = strstr(&(xml[c]), ">");
		  c = loc - xml + 1;
		  continue;
		}
		tag_index[0] = c;
		++c;
		continue;
	  }
	  if (xml[c] == '>') {
		tag_index[1] = c;
		if (tag_index[1] - tag_index[0] < 0)
		  return 1;
		char word[tag_index[1] - tag_index[0] + 2];
		strncpy(word, &(xml[tag_index[0]]), tag_index[1] - tag_index[0]+1);
		word[tag_index[1]-tag_index[0]+1] = '\0';


		if (strcmp(word, operations[op]) == 0) {
		  op+=1;
		  c+=1;
		  if (!operations[op]) {
			elog_err_print("Invalid operation format in xml_printf\n");
			return 4;
		  }
		  if (!operations[op+1]) {
			spot = &(xml[c]);
			break; //END OF LOOP IS HERE!
		  }
		  continue;
		} else {
		  char *endTag = elog_xml_endTag(word);
		  char *loc = strstr(&(xml[c]), endTag);
		  free(endTag);
		  if (!loc)
			return 2;
		  c = loc - xml + (tag_index[1]-tag_index[0]+1) + 1; //sizeof = 1
		  continue;
		}
	  }
	  ++c;
	}
	if (!spot)
	  return 3;

	int is_xml = 0;
	if (operations[op][1] == 'x') 
	  is_xml = 1;
	    
	//commented lines are reticent of fancy printing of old
	//also need to add 1 in the first line of the for loop to the last index.
	//len_ins += depth * 3 + 1 + 3;	// for the tabs and returns
	char *endtag = elog_xml_endTag(tag);
	
	if (!is_xml) {
	  c=0;
	  for (; data[c]; ++c) {
		if (data[c] == '<' || data[c] == '>') {
		  if (!c)
			++len_ins;
		  else if (data[c-1] != '\\')
			++len_ins; //was it already escaped?
		}
		++len_ins;
	  }
	} else {
	  len_ins += strlen(data);
	}
	
	char *final = malloc((sizeof *final) * (len_ins + 2));
	final[0] = '\0';

	char *tmp = final;

	tmp = xml__cat(tmp, tag);
	if (!is_xml)
	  tmp = dat__cat(tmp, data);	// copying in data
	else
	  tmp = xml__cat(tmp, data);
	tmp = xml__cat(tmp, endtag);


	char *save;
	elog_sp_cat(&save, spot, NULL);	// saving last half

	spot[0] = '\0';
	strcat(spot, final);	// assuming there is room!!!
	strcat(spot, save);

	free(save);
	free(final);
	free(endtag);
	elog_sp_ArrFree(operations);

	return 0;

}




//TODO: Clean this function up as much as possible...
//TODO:  Try commenting it too ;).
void elog_xml_scanf(const char *meta, char *template, void *res)
{
  char **operations = elog_sp_breakToArr(template, '.');
  if (operations == NULL)
	return;// error

  char location[strlen(template)];
  location[0] = '\0';

  char last = '\0';
  int i = 0;
  int c = 0;
  while (operations[c+1]) {
	while (meta[i]) {
	  if (meta[i] == '\\') {
		i += 2;
		continue;
	  }
	  
	  if (meta[i] == '<' && meta[i+1] != '/') {
		//see if it could be an op
		int j = 1;
		while (operations[c][j] && meta[i + j] && 
			    operations[c][j] == meta[i + j] && 
			    operations[c][j] != '>' && 
			   meta[i + j] != '>') ++j;
		if (operations[c][j] == '>' && meta[i+j] == '>') {
		  strcat(location, operations[c]);


		  while (meta[i] && meta[i] != '>') ++i;
		  ++i; //going past end of tag
		  last = '>';
		  break; //goto OUTER LOOP!
		} else { //it's not
		  while (meta[i+j] != '>' && meta[i+j]) ++j;
		  
		  char word[j+1];
		  int l;
		  for (l=1; l <= j; ++l)
			word[l] = meta[i+l];
		  word[l] = '\0';
		  word[0] = '<';

		  char *end = elog_xml_endTag(word);
		  char *ptr = strstr(&(meta[i+l]), end);
		  if (!ptr) {
			elog_err_print("Couldn't find end!\n");
			elog_err_print(word);
			elog_err_print(&(meta[i+l]));
			return; 
		  }
		  /*
			  We're skipping ahead because there's no sense looking at things
			    we can't really see..
		  */
		  i += (ptr - &(meta[i+j+l])) + 2*l+1;
		  continue;
		}
	  }

	  if (meta[i] == '/' && last == '<') {
		char *last = NULL;
		int a = strlen(location)-2;
		while (a > 0) {
		  if (location[a] == '.' && location[a+1] == '<' &&
			  location[a-1] == '>') {
			last = &(location[a+1]);
			break;
		  }
		  --a;
		}
		if (!last) {
		  elog_err_print("End tag before begin tag\n");
		  return;
		}

		//++i, offset by the "<" at the beginning of last.
		int j = 1;
		while (last[j] && meta[i + j] && 
			    last[j] == meta[i + j] && 
			    last[j] != '>' && 
			   meta[i + j] != '>') ++j;
		if (last[j] == '>' && meta[i+j] == '>') {
		  *(--last) = '\0';
		  //removed last item...
		} else {
		  elog_err_print("Tags out of order\n");
		  return;
		}
	  }

	  last = meta[i];
	  ++i;
	} 
	++c;
  }

  //i= index into meta where we need to be!
  //c= index of operations opcode ("%s", "%i", etc)


  int l=0;
  while (meta[l+i]) {
	if (meta[l+i] == '\\')
	  l+=1;
	else if (meta[l+i+1] == '/' && meta[l+i] == '<') {
	  int a;
	  for (a=2; meta[l+i+a] == operations[c-1][a-1] &&
				  operations[c-1][a]; ++a) {}
	  if (!(operations[c-1][a])) { break; }
	  else l+=a;
	}
	++l;
  }

  int breaks = 0;
  char *value = malloc(l + 1);
  int stop = l;
  l=0;
  while (l < stop) {
	value[l-breaks] = meta[l+i];
	++l;
  }
  value[l] = '\0';

  switch (operations[c][1]) {
  case 'x':
	{ 
	  char **ans = res;// removing void pointer.
	  *ans = value;
	  break;
	}
  case 'i':
	{
	  char *tmp = elog_sp_whiteSpaceRemove(value);
	  int *i = res;
	  *i = elog_sp_stringToShort(tmp);
	  free(tmp);
	  free(value);
	  break;
	}
  case 's':
	{
	  /* removing escapes */
	  int i=0;
	  int rem=0;
	  while(value[i]) {
		if (value[i] == '\\') {
		  rem++;
		}
		value[i] = value[i+rem];
		++i;
	  }
	  char **ans = res;
	  *ans = value;
	  break; //this needs to filter out '\\'
	}
  }

  elog_sp_ArrFree(operations);
}


unsigned int elog_xml_getShort(char *tag, char *doc)
{
	char *x = elog_xml_tagCapture(doc, tag);
	char *y = elog_sp_whiteSpaceRemove(x);
	free(x);
	unsigned int z = elog_sp_stringToShort(y);
	free(y);
	return z;
}

char *elog_xml_getChar(char *tag, char *doc)
{
	char *x = elog_xml_tagCapture(doc, tag);
	char *y = elog_sp_whiteSpaceRemove(x);
	free(x);
	return y;
}
void **elog_xml_scanl(const char *meta, const char *template,
		      const char *tag, void *(*callback) (char *))
{
	void **ret = malloc((sizeof *ret) * 1024);
	// we have an inherant limit of one thousand attachments.
	// that should cover people well.

	char *xml = NULL;
	char *template2;
	char *end = elog_xml_endTag(tag);
	int end_len = strlen(end);

	// first, we'll add some necessary scanf syntax
	elog_sp_cat(&template2, template, ".%s", NULL);
	elog_xml_scanf(meta, template2, &xml);
	free(template2);
	if (!xml) {
		free(end);
		free(xml);
		free(ret);
		return NULL;
	}
	// now we have, in xml, the xml structure within template (which is an 
	// xml path).

	int c = 0;
	char *p = strstr(xml, tag);	// the beginning of one of 
	// our tags
	while (p) {
		if (c > 1024) {
			free(end);
			free(xml);
			return ret;	// nasty failsafe
		}

		char *p2 = strstr(p, end);
		if (!p2) {
			p = strstr(&(p[end_len - 1]), tag);
			continue;
		}
		char bk = p2[end_len];
		p2[end_len] = '\0';	// the end of one tag

		void *q = callback(p);


		p2[end_len] = bk;
		p = strstr(&(p[end_len - 1]), tag);
		if (q)
			ret[c] = q;
		else
			continue;	// callback failed, start
		// on next root tag
		++c;
	}
	ret[c] = NULL;


	free(xml);
	free(end);
	return ret;
}



void
elog_xml_printl(char *meta, const char *template, const char *tag,
		const char *subTag, int (*callback) (void *, char *,
						     const char *),
		void **data, int estDatSize)
{
	if (!data)
		return;

	int c;
	for (c = 0; data[c] != NULL; ++c);	// I wanna know how many items we
	// have.

	int items = c;
	char *set = malloc((sizeof *set) * (strlen(subTag) + 10 + estDatSize) * (c + 1) * 2);	// this 
	// will 
	// become 
	// a 
	// set 
	// of 
	// tags 
	// like 
	// this:
	/*
	 * <attchmnt> DATA </attchmnt> <attchmnt> DATA </attchmnt> 
	 */

	int depth = 1;
	const char *p1 = template;
	for (; p1; p1 = strstr(p1, "."))
		++depth;	// counting depth.



	set[0] = '\0';
	char *p = set;
	for (c = 0; c < items; ++c) {
		char *entry = elog_xml_start(subTag,
					     (strlen(subTag) + 10 +
					      estDatSize) *
					     (sizeof *entry));
		if (callback(data[c], entry, subTag)) {
			elog_err_print_console
			    ("Error creating a tag in a list\n");
			continue;
		}
		// update depth
		// the following code is essentially for pretty-ification.
		int tabs = 0;
		int d = depth;
		int j;
		for (j = 0; entry[j]; ++j) {
			if (entry[j] == '\n')
				tabs += d;
		}

		d = depth;
		for (; j; --j) {
			if (entry[j] == '\n') {
				int k;
				for (k = 0; k < d; ++k)
					entry[j + tabs - (k)] = '\t';
				entry[j + tabs - k] = entry[j];
				tabs -= d;
			} else
				entry[j + tabs] = entry[j];
		}
		p = xml__cat(p, entry);
		free(entry);
	}


	char *t;
	elog_sp_cat(&t, template, ".%x", NULL);
	elog_xml_printf(meta, t, tag, set);
	free(set);
	// ^ make our root tag, and dumping in data.
}
